package backend;

import java.util.Calendar;
import java.util.List;
import java.util.Observable;
import backend.IO.IOType;

/**
 * Cpu.java
 * 
 * Simulates the CPU for hardware.
 * 
 * @author BitWiseGuys Dallas Rosson, Joshua Warner, Tyson Lewis, Ken Norton
 * @version 1.0, 5/27/2012
 */
public class Cpu extends Observable implements Runnable{

	//io_type :0-no IO, 1-Timer, 2-video, 3-Disk, 4-KeyBoard
	private static final int DEFAULT_LIMIT = 100000;
	
	private final int LIMIT;

	private IO.IOType my_io_type;
	
	private IO.IOType my_io_request_type;

	private IO my_disc;

	private IO my_monitor;

	private IO my_keyboard;
	
	private SchedulerTimer my_timer;

	private Context context_mode;

	private int numSteps;

	private int pc;

	private Interrupt interrupt;

	private Trap_Handle trap_handle;
	
	private System_Handle system_handle;

	private Interrupt_Handle interrupt_handle;

	private PCB register;

	private PCB currentPCB;

	private Scheduler my_scheduler;

	public SharedMemory my_sharedMemory;

	public int memValueRegister;
	
	private boolean run_prog = true;;
	
	private final int WAIT_TIME = 200;

	public enum Context {
		KERNEL_MODE, USER_MODE;
	}

	public enum Interrupt_Handle{
		TIMER_INT_HANDLE, IO_INT_HANDLE,  NO_INTERRUPT;
	}

	public enum Trap_Handle {
		IO_REQ_TRAP_HANDLER, MUTEX_AQUIRE_TRAP_HANDLER,
		MUTEX_GIVE_TRAP_HANDLER, MEMORY_WRITE_TRAP_HANDLER, MEMORY_READ_TRAP_HANDLER, NO_TRAP;
	}

	public enum System_Handle{
		IO_SYSTEM_CALL, MUTEX_AQUIRE_SYSTEM_CALL,
		MUTEX_GIVE_SYSTEM_CALL, MEMORY_WRITE_WRITE_SYSTEM_CALL, MEMORY_READ_SYSTEM_CALL, NO_INTERRUPT;
	}	

	public enum Interrupt{
		DISABLED, ENABLED;
	}

	/**
	 * public constructor to instantiate the class
	 * */
	public Cpu(int the_numSteps, final List<String> the_processes){
		numSteps = the_numSteps;
		LIMIT = the_numSteps;
		my_disc = new IO(IOType.DISK, this);
		my_keyboard = new KeyBoard(IOType.KEYBOARD, this);
		my_monitor = new IO(IOType.MONITOR, this);
		my_timer = new SchedulerTimer(this);
		interrupt_handle = Interrupt_Handle.NO_INTERRUPT;
		pc = 0;
		interrupt = Interrupt.ENABLED;
		my_sharedMemory = new SharedMemory();
		context_mode = Context.KERNEL_MODE;
		my_scheduler = new Scheduler(the_processes);
		trap_handle = Trap_Handle.NO_TRAP;
		system_handle = System_Handle.NO_INTERRUPT;
	}
	/**
	 * Run Process runs the program 
	 * */
	@Override
	public void run(){
		currentPCB = my_scheduler.getNextProcess();
		my_timer.start();
		while (pc < LIMIT && run_prog){
			//Notify observers that there is a change.
			setChanged();
			notifyObservers(this.getOutput());
			
			//Check for interrupts
			if (interrupt_handle == Interrupt_Handle.NO_INTERRUPT &&
					system_handle == System_Handle.NO_INTERRUPT &&
					trap_handle == Trap_Handle.NO_TRAP) {
				
				// run process
					//int value = currentPCB.getProc().runProcess();
				//the process is making a system call then on the next iteration the system call will be set
				
				int current_step = currentPCB.getNextStep();
				IO.IOType request;
				if (currentPCB.getProc().getRequestStep() == current_step){
					request = currentPCB.getProc().nextRequest();
					trap_handle = Trap_Handle.IO_REQ_TRAP_HANDLER;
					my_io_request_type = request;
				}
				currentPCB.setNextStep(current_step + 1);

				
			} else {
				register = currentPCB; //my_scheduler.getNextProcess();//save the state of the PCB
				if (interrupt_handle !=Interrupt_Handle.NO_INTERRUPT){
					interrupt = Interrupt.DISABLED;
					interruptHandler();
					interrupt = Interrupt.ENABLED;
					interrupt_handle = Interrupt_Handle.NO_INTERRUPT;
				}else if(trap_handle != Trap_Handle.NO_TRAP){
					interrupt = Interrupt.DISABLED;
					trapHandler();
					trap_handle = Trap_Handle.NO_TRAP;
					interrupt = Interrupt.ENABLED;

				} else if(system_handle != System_Handle.NO_INTERRUPT){
					systemCallHandler();
				}
			
			}
			System.out.println("PC: " + pc + " LIMIT: " + LIMIT);
			pc++;
//			if (pc<=LIMIT){
//				pc = 0;
//			}
			try{
				Thread.sleep(WAIT_TIME);
			}catch(Exception e){
				
			}
			
		}

	}	


	/**CPU Helper functions**/
	/********************************************************************************/
	/**
	 * Timer interrupt handler gets called when the time slice is up
	 * place the current running process in the end of the ready queue
	 *  call the scheduler for the next process to run
	 *  run the current process
	 * **/

	void timer_int_handler(){
//		my_scheduler.schedule(register);
//		currentPCB = my_scheduler.getNextProcess();
		
		currentPCB = my_scheduler.schedule(currentPCB);
		
	}

	/**
	 * io interrupt handler gets called when an io interrupt occurs.
	 * **/
	void io_int_handler(){

		//TODO  handle the interrupt and handle the current process in the waiting io blocking queue.
		if (my_io_type == IOType.DISK){
			//poll from the io queue and place the pcb in the ready queue
			if (!my_disc.waitQueueIsEmpty()){
				PCB var_pcb = my_disc.getPCB();
				//my_scheduler.schedule(var_pcb);
				my_scheduler.addToReadyQ(var_pcb);

			}
		}else if (my_io_type == IOType.KEYBOARD){
			if (!my_keyboard.waitQueueIsEmpty()){
				PCB var_pcb = my_keyboard.getPCB();
				//my_scheduler.schedule(var_pcb);
				my_scheduler.addToReadyQ(var_pcb);
			}
		}else if(my_io_type == IOType.MONITOR){
			if (!my_monitor.waitQueueIsEmpty()){
				PCB var_pcb = my_monitor.getPCB();
				//my_scheduler.schedule(var_pcb);
				my_scheduler.addToReadyQ(var_pcb);
			}
		}

	}

	/**
	 * io request handler gets called when an io request occurs.
	 * **/
	void io_req_trap_handler(){

		
		//determine IO to access
		if (my_io_request_type == IOType.DISK){
			my_disc.addRequest(currentPCB);
			//get next pcb
			currentPCB = my_scheduler.getNextProcess();
		}else if (my_io_request_type == IOType.KEYBOARD){
			my_keyboard.addRequest(currentPCB);
			//get next pcb
			currentPCB = my_scheduler.getNextProcess();
		}else if(my_io_request_type == IOType.MONITOR){
			my_monitor.addRequest(currentPCB);
			//get next pcb
			currentPCB = my_scheduler.getNextProcess();
			
		}else if(my_io_request_type == IOType.SHARE_MEM_READ){
			trap_handle = Trap_Handle.MEMORY_READ_TRAP_HANDLER;
			//memory_read_trap_handler();
		}else if(my_io_request_type == IOType.SHARE_MEM_WRITE){
			trap_handle = Trap_Handle.MEMORY_WRITE_TRAP_HANDLER;
			//memory_write_trap_handler();
		}
	}

	/**
	 *
	 * Trap to acquire the mutex if possible, if not place in the Mutex waiting queue.
	 * **/
	void mutex_acquire_trap_handler(){
		if(my_sharedMemory.isLocked()){
			my_sharedMemory.addToWaitingQ(currentPCB);
		} else {
			my_sharedMemory.setMutexLockOwner(currentPCB);
			my_sharedMemory.lock_mutex();
		}
	}



	/**
	 *
	 * Trap to give up the mutex.
	 * **/
	void mutex_give_trap_handler(){

		my_sharedMemory.unlock_mutex();
		my_sharedMemory.setMutexLockOwner(my_sharedMemory.getWaitingPCB());
	}

	/**
	 * Trap to try to write to memory, if the write lock is available
	 *
	 * */

	void memory_write_trap_handler(){
		//TODO code to write to the shared memory location should lock the read mutex/semiphore
	//	mutex_acquire_trap_handler();
		if (my_sharedMemory.isLocked() && my_sharedMemory.getLockOwner().equals(currentPCB)){	
			my_sharedMemory.setMemory(memValueRegister, currentPCB);
			trap_handle = Trap_Handle.MUTEX_GIVE_TRAP_HANDLER;
		} my_sharedMemory.addToWaitingQ(currentPCB);
		//mutex_give_trap_handler();
	}

	/**
	 * Trap to try to read to memory, if the mutex lock is available
	 * Calls the system_call() to read memory
	 * */
	void memory_read_trap_handler(){

		if(my_sharedMemory.isLocked() && my_sharedMemory.getLockOwner().equals(currentPCB)){	
			memValueRegister = my_sharedMemory.getMemory();
			trap_handle = Trap_Handle.MUTEX_GIVE_TRAP_HANDLER;
		}
		//mutex_give_trap_handler();
		 my_sharedMemory.addToWaitingQ(currentPCB);
	} 

	/**
	 * System call to actually do the io opperation.
	 *
	 * */
	void io_system_call(){

		this.trap_handle = Trap_Handle.IO_REQ_TRAP_HANDLER;
	}

	/**
	 * System call to actually do the mutex get opperation.
	 *
	 * */
	void mutex_acquire_system_call(){

		this.trap_handle = Trap_Handle.MUTEX_AQUIRE_TRAP_HANDLER;
	}

	/**
	 * System call to actually do the mutex give opperation.
	 *
	 * */
	void mutex_give_system_call(){

		this.trap_handle = Trap_Handle.MUTEX_GIVE_TRAP_HANDLER;
		
	}

	/**
	 * System call to actually do the memory write opperation.
	 * @param takes a parameter of the data to be written
	 * */
	void memory_write_system_call(){

		this.trap_handle = Trap_Handle.MEMORY_WRITE_TRAP_HANDLER;


	}

	/**
	 * System call to actually do the memory read opperation.
	 * @return returns the data in memory.
	 * */
	void memory_read__system_call(){
		this.trap_handle = Trap_Handle.MEMORY_READ_TRAP_HANDLER;
	}



	void interruptHandler(){

		switch (interrupt_handle){
		case IO_INT_HANDLE: io_int_handler();
		break;
		case TIMER_INT_HANDLE: timer_int_handler();
		break;
		}
	}

/**
 * Handle the traps
 * */
	void trapHandler(){

		switch (trap_handle){

		case IO_REQ_TRAP_HANDLER: io_req_trap_handler();

		break;

		case MUTEX_AQUIRE_TRAP_HANDLER: mutex_acquire_trap_handler();

		break;

		case MUTEX_GIVE_TRAP_HANDLER: mutex_give_trap_handler();

		break;

		case MEMORY_WRITE_TRAP_HANDLER: memory_write_trap_handler();

		break;

		case MEMORY_READ_TRAP_HANDLER: memory_read_trap_handler();

		break;
		}
	}


/**
 * Handle the system call
 * */
	void systemCallHandler(){

		switch (system_handle){

		case IO_SYSTEM_CALL: io_system_call();

		break;

		case MUTEX_AQUIRE_SYSTEM_CALL: mutex_acquire_system_call();

		break;

		case MUTEX_GIVE_SYSTEM_CALL: mutex_give_system_call();

		break;

		case MEMORY_WRITE_WRITE_SYSTEM_CALL: memory_write_system_call();

		break;

		case MEMORY_READ_SYSTEM_CALL: memory_read__system_call();

		break;



		}

	}
	public String getOutput(){
		String var_handle = "";
		StringBuffer result = new StringBuffer();
		long timeInMillis = System.currentTimeMillis();
		Calendar cal = Calendar.getInstance();
		cal.setTimeInMillis(timeInMillis);
		java.util.Date date = cal.getTime();
		if (system_handle != System_Handle.NO_INTERRUPT){
			var_handle = system_handle.toString();
		}else if(interrupt_handle != Interrupt_Handle.NO_INTERRUPT){
			var_handle = interrupt_handle.toString();
		}else if (trap_handle != Trap_Handle.NO_TRAP){
			var_handle = trap_handle.toString();
		}
		result.append("Current Process ID: " + String.valueOf(currentPCB.getPid())+" "+ date +" "+ var_handle);
		
		return result.toString();
	}

	public IO.IOType getIo_type() {
		return my_io_type;
	}
	/**
	 * 
	 * @param IO.IOType 
	 * */
	public void setIo_type(IO.IOType io_type) {
		this.my_io_type = io_type;
	}
/**
 * @return the interrupt enum state DISABLED or ENABLED
 * */
	public Interrupt getInterruptState() {
		return interrupt;
	}
	/**
	 * @param interrupt sets the state of the interrupt enum state DISABLED or ENABLED
	 * */
	public void setInterruptState(Interrupt interrupt) {
		this.interrupt = interrupt;
	}
	/**
	 * @param int_handle
	 * */
	public void setInterruptHandle(Interrupt_Handle int_handle){
		this.interrupt_handle = int_handle;
	}
	/**
	 * @returns system_handle
	 * */
	public System_Handle getSystemHandle() {
		return system_handle;
	}

	public void setSystemHandle(System_Handle the_system_handle) {
		this.system_handle = the_system_handle;
	}

	public Trap_Handle getTrapHandle() {
		return trap_handle;
	}

	public void setTrapHandle(Trap_Handle the_trap_handle) {
		this.trap_handle = the_trap_handle;
	}

	/**
	 * @returns list of PCB's int the waiting queue as a string
	 * */
	public String getKeyboardQ(){
		return my_keyboard.getWaitinglist();
	}
	/**
	 * @returns list of PCB's in the waiting queue as a string
	 * */
	public String getMonitorQ(){
		return my_monitor.getWaitinglist();
	}
	/**
	 * @returns list of PCB's in the waiting queue as a string
	 * */
	public String getDiscQ(){
		return my_disc.getWaitinglist();
	}
	/**
	 * @returns the ready queue from the scheduler
	 * */
	public String getReadyQ(){
		return my_scheduler.getReadyQueue();
	}
	/**
	 * @returns the shared memory owner as a string
	 * */
	public String getMutexOwner(){
		return my_sharedMemory.getMutexOwnerString();
	}
	/**
	 * @returns the shared memory value as a string
	 * */
	public String getSharedMemoryValue(){
		return my_sharedMemory.getMemoryString();
	}
	/**
	 * @returns list of PCB's in the waiting queue as a string
	 * */
	public String getMutexWaitList(){
		return my_sharedMemory.getWaitinglist();

	}
	/**
	 * @returns the keyboard.
	 * */
	public IO getKeyboard(){
		return my_keyboard;
	}
	
	public void stopCPU(){
		run_prog = false;
	}
}